package elastic

import (
	"context"
	"errors"
	"fmt"
	"net"
	"net/url"
	"reflect"
	"regexp"
	"strings"
	"sync"
	"time"

	"github.com/go-kit/kit/log"
	"github.com/go-kit/kit/log/level"
	elastic "gopkg.in/olivere/elastic.v5"
)

// ZA: elastic as store
type elasticOutput struct {
	Enable bool
	Client *elastic.Client
	logger log.Logger
	mux    sync.Mutex
}

var (
	ElasticOutput = &elasticOutput{}
	DateFormat    = "2006.01.02"
	timeFormat    = "2006-01-02T15:04:05.000Z"
	// Index         = "alertmanager" + "-" + time.Now().Format(DateFormat)
	Index = "alertmanager-event"
)

// NewClient create elastic client
func NewClient(hosts []string, user string, password string, logger log.Logger) {
	esURLs := getURL(hosts)
	c, err := elastic.NewClient(elastic.SetSniff(false),
		elastic.SetBasicAuth(user, password),
		elastic.SetURL(esURLs...),
		elastic.SetMaxRetries(3))
	if err != nil {
		level.Error(logger).Log("msg", err)
		return
	}
	ElasticOutput.Client = c
	ElasticOutput.Enable = true
	ElasticOutput.logger = logger
	level.Info(logger).Log("msg", "connect elastic success")
}

// getURL new elastic url
func getURL(hosts []string) []string {
	var hasScheme = regexp.MustCompile(`^([a-z][a-z0-9+\-.]*)://`)
	var esURLs []string

	for _, esHost := range hosts {
		rawURL := esHost

		if !hasScheme.MatchString(rawURL) {
			rawURL = fmt.Sprintf("http://%v", rawURL)
		}

		addr, err := url.Parse(rawURL)
		if err != nil {
			continue
		}

		scheme := addr.Scheme
		host := addr.Host
		port := "9200"

		if host == "" {
			host = "localhost"
		} else {
			// split host and optional port
			if splitHost, splitPort, err := net.SplitHostPort(host); err == nil {
				host = splitHost
				port = splitPort
			}

			// Check if ipv6
			if strings.Count(host, ":") > 1 && strings.Count(host, "]") == 0 {
				host = "[" + host + "]"
			}
		}

		// Assign default path if not set
		if addr.Path == "" {
			addr.Path = ""
		}

		// reconstruct url
		addr.Scheme = scheme
		addr.Host = host + ":" + port
		esURLs = append(esURLs, addr.String())
	}
	return esURLs
}

// Output exec elastic store an alarm
func (es elasticOutput) Output(field map[string]interface{}) {
	c := ElasticOutput.Client
	if c == nil {
		return
	}
	ctx := context.Background()
	logger := es.logger

	// Lock : the first time multiple events arrive, resulting in repeated index creation, causing data loss problem.
	ElasticOutput.mux.Lock()
	exists, err := c.IndexExists(Index).Do(ctx)
	if err != nil {
		level.Error(logger).Log("msg", fmt.Sprintf("index %v exists %v err %v", Index, exists, err.Error()))
		return
	}

	if !exists {
		mapping := `
			{	
				"settings":{
					"number_of_shards":5,
					"number_of_replicas":1
				},
				"mappings":{
					"doc":{
						"properties":{
							"time":{
								"type":"date"
							}
						}
					}
				}
			}`

		cIndex, err := c.CreateIndex(Index).Body(mapping).Do(ctx)
		if err != nil {
			level.Error(logger).Log("msg", err)
			return
		}
		if !cIndex.Acknowledged {
			level.Error(logger).Log("msg", errors.New("create index fail"))
			return
		}
	}
	ElasticOutput.mux.Unlock()

	// Solve time zone issues
	// example time: 2019-03-08T03:10:19.000Z
	// var errMsg string
	local, err := time.LoadLocation("Local")
	if err != nil {
		level.Error(logger).Log("msg", err)
	}

	if field["time"] != nil {
		t := reflect.ValueOf(field["time"]).String()
		t1, err := time.ParseInLocation(timeFormat, t, local)
		if err != nil {
			field["error"] = "wrong time format."
			field["ctime"] = t
			field["time"] = time.Now().UTC().Format(timeFormat)
		} else {
			field["time"] = t1.UTC().Format(timeFormat)
		}
	} else {
		field["time"] = time.Now().UTC().Format(timeFormat)
	}

	// put alert event to elastic
	_, err = c.Index().Index(Index).Type("doc").BodyJson(field).Do(ctx)
	if err != nil {
		level.Error(logger).Log("msg", err)
	}
}
